类
一、定义类与对象
1、定义类
class classname
{
public:
//公有数据成员、成员函数;
private:
//私有数据成员、成员函数;
protected:
//保护数据成员、成员函数;
};
一定一定注意末尾要有分号作为结束类的定义标志
数据成员、成员函数可以为空。
访问权限:
可访问性:
公有成员可访问性最强,所有东西都可以访问
保护的成员可访问性一般,在自己这个类和派生类中都能访问。(除了友元)
私有成员可访问性最弱,只有自己的类可以访问。(除了友元)
2、定义对象
class classname
{
public:
//公有数据成员、成员函数;
private:
//私有数据成员、成员函数;
protected:
//保护数据成员、成员函数;
};
//上面已经定义了一个类classname,现在用这个类定义对象,如下:
classname object_name;//定义了一个名字叫object_name的类的类型为classname的对象
3、类中成员的定义
类中的变量和普通变量定义一毛一样,但是不能赋初值。
类中的函数需要在类内申明,类外定义(和一般函数申明也一样)。(除了友元函数)一般来说,函数要写在公有部分。类外的定义要注意函数的名字
class classname
{
public:
int name (int,int);//函数类内申明
//公有数据成员、成员函数;
private:
//私有数据成员、成员函数;
protected:
//保护数据成员、成员函数;
};
int classname::name (int para1,int para2)//函数类外定义
{
//......
}
特例:内联函数直接放在类中定义就行 可以不申明inline,编译器会把类中定义的直接编译为内联函数
4、this指针
this指针是一个隐藏的指针,不用定义就会自动产生,专门用于类的成员函数。当一个对象的成员函数调用这个函数,这个对象的首地址传给this指针,所以就可以在函数内使用this->member来访问这个对象的member成员。
5、成员的访问
访问对象的格式为:对象.数据成员
访问classname类的A对象中的一个数据health,就是
classname A;//定义一个对象
int a=A.health;//读取对象A的health成员赋值给a
A.health=100;//把100写入对象A的health成员地址中
再次说明:在主函数中只能访问到公有成员
二、类的成员
构造函数是这个对象一建立起来就会运行的函数。析构函数是这个对象一删除就会运行的函数。这两个函数都不能返回值。
1、构造函数
构造函数可以有参数,所以可以重载,构造函数的名字要和类名相同
class classname
{
public:
classname();//构造函数
classname(int);//构造函数重载一号
classname(int,int);//构造函数重载二号
classname(double);//构造函数重载三号
//公有数据成员、成员函数;
private:
//私有数据成员、成员函数;
protected:
//保护数据成员、成员函数;
};
和普通函数一样,构造函数也是类内申明,类外定义,但是不用说明返回类型(因为编译器知道这是构造函数,不会有返回值)
classname::classname ()//函数类外定义
{
//......
}
构造函数用途很多,比如输出内容,让编写者知道这个类在这时候建立起来了
classname::classname ()
{
cout<<"这个类现在建立了哦";
}
一旦用classname这个类来构造一个对象就会输出“这个类现在建立了哦”。
构造函数最常见的用途是给类中的元素赋初值,比如下面是某地警察的犯罪记录次数系统:
class fanzuicishu
{
public:
fanzuicishu();
fanzuicishu(string,int);
private:
string name;
int times;
};
fanzuicishu::crime ()
{
cout<<"您搁这输入空气呢?";
}
fanzuicishu::crime (string input_name,int input_times)
{
name=input_name;
times=input_times;
}
当主函数中有:
fanzuicishu zs;
zs();
就会输出:您搁这输入空气呢?
如果这样:
fanzuicishu zs;
zs(zhangsan,20);
那么zs这个对象的数据成员name就会被赋值zhangsan,times会被复制20;这样就不用新开一个函数来单独修改新开对象zs的name和times。
这样一来,张三的名字(zhangsan)和犯罪次数(20次)在zs这个对象一建立的时候就被写进了警察局的系统里面。(当然现实中肯定没这么低级)
拷贝构造函数
将构造函数重载为可以把数据成员复制到新的一个对象的类上面,比如某个游戏中一个人有金钱和生命两个数据:
class classname
{
public:
classname();//默认构造函数
classname(int money,int health);//重载构造函数为开辟一个人的金钱和生命的内存空间并初始化,定义略
classname(const classname &,int =1,int =1 );//重载为拷贝构造函数,和默认构造函数的区别就是有后两个参数,使编译器知道你在调用这个重载函数而不是默认构造函数,定义在下面
private:
int money;
int health;
protected:
};
classname::classname(const classname &A,int x=1,int y=1)//拷贝构造函数定义
{
money=A.money;//复制金钱
health=A.health;//复制生命
}
...//其他函数定义略
int main()
{
classname A(100,10);//A的人有金钱100money,有生命10health
classname B(A,0,0);//利用三个参数使编译器知道你在调用拷贝构造函数,新开对象B这个人,并且把A这个人的金钱,生命复制给B
}
2、析构函数
析构函数在类被删除的时候运行,一般用于告诉程序员,这个对象消失了,或者清除类中开的内存。和构造函数一个道理,但是不能加入参数。
析构函数也是类内申明,类外定义。格式为:同构造函数,与类名字一样,前面加上:~,如下:
class classname
{
public:
classname();//构造函数
~classname();//析构函数
private:
protected:
}
注意:析构函数不加参数,所以不能重载
3、常数据成员
就是和平时都常数一样,前面加const就好,直接在类内说明时定义值即可。
4、常对象
申明对象时,前面加上const,常对象只读
class classname
{
public:
...
private:
...
protected:
};
const classname A(1,2);//申明常对象,常对象只能调用构造函数赋初始值,一旦赋值,不能再改
5、常成员函数
常成员函数不能修改类中的数据成员,只能读
函数申明时和定义时,参数表的后面加上const,其他和普通成员函数一样
class classname
{
public:
int function1(int,int) const;//末尾加const
private:
...
protected:
};
int classname::function1(int parameter1,int parameter2) const //定义后面加const
{
...
}
6、静态成员
静态成员是所有这个类的对象所公有的
一个例子
比如十个人拥有自己的属性,那么这十个人就是属于人这个类的十个对象,每个对象有自己的属性,但是这十个人共用一张银行卡,这个银行卡里面面的钱数量受到所有人的控制。假设账户原来有1000块,第一个人把银行卡花了100块,账户里剩下900块,900由这十个人共享,不属于任何一个人。
上面那个例子中,人是类,每个人是属于类的对象,而银行卡的钱数量是静态数据成员。
类内申明静态成员时,前面加上static,类外定义不必再说明static。静态成员不属于任何一个对象,是公共的。
静态成员也是在类外定义,不初始化默认为0,初始化需要用类名初始化而不是某个对象(这也说明静态成员不属于任何一个对象)。
静态成员详情
静态成员在程序开始运行时就分配内存空间(分配空间初始化值为0),程序结束才释放空间,所以一直存在,不必在主函数开始运行后(或者定义对象之后)才赋值。
class classname
{
public:
static int money;//静态成员:钱的数量
private:
...
protected:
};
int classname::money=1;//初始化用类的名字,不是对象,静态成员不初始化默认为0,不必在主函数中赋值
int main()
{
classname object;
}
7、静态成员函数
静态成员函数只能访问类的静态数据成员,不能用于其他用途
class classname
{
public:
static int money;//静态成员:钱的数量
static int get_money(int);//静态成员函数:获取金钱数量
static int add_money(int);//静态成员函数:增加金钱数量
private:
...
protected:
};
int classname::get_money(int the_money)
{
...
}
int classname::add_money(int the_money)
{
...
}
int main()
{
...
}
静态成员函数也是类内申明static类外不必static
三、友元
1、友元的性质
类classname的友元可以直接访问classname这个类的所有数据成员,包括私有。
友元可以是普通函数,可以是成员函数,也可以是另外一个类
类classname的友元可以直接访问classname这个类的所有数据成员,包括私有。
友元不具有对称性和传递性,比如B函数是A的友元,B可以使用A的所有成员,但除非特殊说明,A不可以用B的成员。A是B的友元,B是C的友元,但A不能用C中的数据成员
2、友元的申明与定义
说明函数f是类A的友元,直接在类A的f函数前面加上friend
class A
{
public:
...
friend void f();//申明f是类A友元
private:
...
int the_thing_he_knows ;
protected:
...
};
这样f就可以直接访问类A的the_thing_he_knows
在定义的时候,不需要void A::f()因为友元不属于这个类,把f放在类A中并且加上friend只是为了说明f是类A的友元
void f()//外部定义不需要表明f在A中,也不需要说明friend,直接当有访问权限的普通函数定义。
{
cout<<the_thing_he_knows;
}
友元类同理,申明B是A的友元类,则类A中所有东西类B可以随便使用
四、类的继承与派生
1、有关继承和派生
关于类继承和派生的一个比喻
继承和派生其实是一个东西的两种说法
儿子继承了爸爸的基因,爸爸派生出儿子,差不多这个意思
类B继承类A,则类B中除了有从类A中继承过来的成员,还有自己的成员。而A没有B的成员。
不恰当的比喻:爸爸A把基因全部传给儿子B,儿子B有爸爸A全部基因,自己还额外出现一些基因。爸爸A没有儿子B新出现的基因。
另外一个比喻
类A:植物
类B:低等植物
类C:高等植物
那么B、C两个类是由A派生出来的,继承了A的属性。A有点属性,B、C都有。
植物有属性:由细胞组成,那么其派生类B、C继承了这个属性,由细胞组成
但是高等植物C有植物A没有的属性,低等植物B也有植物A没有的属性。
在这里,植物类A是基类,B、C是派生类
被继承的类(爸爸)相对于继承的类(儿子)叫做基类,继承的类(儿子)叫做派生类。
用图表示:箭头由派生类指向基类,如下图,A是基类,B、C是A的派生类
继承关系是相对的,比如下图
A是B的(直接)基类,A是C的(间接)基类,B是C的基类
2、继承的访问权限
继承总共有三类:
- 公有继承
- 私有继承
- 保护继承
继承规则:
| 继承类型 | 继承前(基类)可访问性 | 继承后(派生类)可访问性 |
| 公有继承 | 公有 | 公有 |
| 保护 | 保护 | |
| 私有 | 不可继承 | |
| 私有继承 | 公有 | 私有 |
| 保护 | 私有 | |
| 私有 | 不可继承 | |
| 保护继承 | 公有 | 保护 |
| 保护 | 保护 | |
| 私有 | 不可继承 |
对上表的理解
表看起来复杂,实则有规律可循
私有成员不能被继承:前文说过,类的私有数据只有在类内可以访问,在类外和派生类都不能访问,所以以下都抛开基类私有成员
公有继承下,公有还是公有,保护还是保护
保护继承下,全部变成保护
私有继承下,全部变成私有
一般程序都是用公有继承,保护继承和私有继承很少用
3、继承的写法
先构造一个植物类
class plants
{
public:
...
private:
...
protected:
...
};
然后把plants作为基类,构造高等植物和低等植物派生类
class higher_plants:public plants//*
{
public:
...
private:
...
protected:
...
};
class lower_plants:public plants//*
{
public:
...
private:
...
protected:
...
};
就是像打*号的地方这样,在类名字后面跟上继承类型public和基类的名字plants
派生类可以有多个(直接)基类,如果一个派生类有不止一个(直接)基类
class higher_plants:public plants, public type_1, private type_2
{
public:
...
private:
...
protected:
...
};
像这样,写出所有继承类型直接基类的名字并用逗号隔开
4、访问申明
恢复继承过程中被降低可访问性的成员的可访问性
格式:
class plants
{
public:
string set_name(string name);//设置植物名字
int set_height(int value);//设置植物高度
int set_radius(int value);//设置切面半径
int count;//植物数量
...
private:
string name;//植物名字
...
protected:
double height;//植物高度
double radius;//植物切面半径
...
};
比如,原本基类plant中公有成员count,在私有继承中count原本会变成私有。特地申明一下就恢复count为公有成员
申明的注意事项:
- 私有成员不参与任何派生类的活动(除非特殊说明)。
私有变量height和radius无法被继承,更不用说申明了。
- 申明时仅仅申明名字(附加作用域)。如果是函数,返回类型和参数都不用写;如果是变量,数据类型不用写。只用写出 “作用域::名字”(可以在作用域前面加上using)
class higher_plants:public plants, public type_1, private type_2
{
public:
plants::set_name;//直接把函数名(带上作用域)摆在这里
using plants::count;//直接把变量的名字摆出来(using可省略)
...
private:
...
protected:
...
};
- 基类和派生类中访问申明的成员必须同一可访问性。(这一点有争议,我自己试了在public和private中可以自由改变)
count在基类是公有变量,申明后必须还是公有。不能申明完就变成保护了。
对于同名的重载函数(一般人都不会碰到这种情况)
(1)两个函数都在基类中,而且在同一访问域,两个申明都有效。
f() f(int)两个函数都是公有的,申明完都变成公有
(2)两个函数都在基类中,然而不在同一访问域,不能访问申明。
f()公有 f(int)私有,申明有歧义,不能申明
(3)一个函数在基类中,一个函数在派生类中,不能访问申明。
f()在基类A中,f(int)在A的派生类A'中,申明有歧义,不能申明
5、重名的成员
1、数据成员重名
基类中有height为名字的数据,派生类中又有定义一个叫height名字的数据。两个数据会分别建立存储空间,相当于两个不同的变量。
不写作用域默认为该类的成员,如果要弄到基类,需要标注作用域(作用域::成员)
class person
{
public:
int age;//人的年龄
...
private:
...
protected:
...
};
class student:public person
{
public:
int age;//学生的年龄
...
private:
...
protected:
...
};
int main()
{
person me;
student he;
me.age=18;//person类的age
he.age=18;//student类的age
me.person::age=18;//person类的age
}
2、成员函数重名
同名的成员函数一个位于基类,一个位于派生类,派生类会把基类的屏蔽掉(而不是会寻找重载)。
class person
{
public:
void get_age();//获得人的年龄
void get_age(int rank);//获得第n个人的年龄,函数重载
...
private:
...
protected:
...
};
class student:public person
{
public:
void get_age(int,int);//获得学生的年龄
...
private:
...
protected:
...
};
int main()
{
student ten_students;
ten_students.get_age();//错误,student类中的get_age(int,int)屏蔽了person中的get_age(),找不到对应重载函数,编译错误
ten_students.get_age(5);//错误,student类中的get_age(int,int)屏蔽了person中的get_age(int),找不到对应重载函数,编译错误
ten_students.person::get_age();//正确,指定基类person为作用域
person five_persons;
five_persons.get_age();//正确
five_persons.get_age(int);//正确
}
被屏蔽的基类函数要通过作用域来访问(作用域::成员)
6、派生类中的静态成员
派生类直接和基类共享该静态成员(除非静态成员私有)
7、基类的初始化
派生类从基类继承的数据如果要通过构造函数初始化,需要调用基类的构造函数给这些继承过来的数据赋初值。
下面讲一下初始化列表
在构造函数后加上“:data(value)”,然后用逗号隔开,构成初始化列表,可以给data这个数据初始化赋值value。初始化列表必须加在函数的定义处,初始化是按照顺序来的,先传参数,由初始化列表从前到后赋值,最后再执行函数体。
class person
{
public:
int a;
int b;
int c;
person(int age):a(age),b(age),c(a){};//把age赋给a,b。把a赋给c,并且直接把函数当内联函数定义
person(int,int);
...
private:
...
protected:
...
};
person::person(int age,int rank):a(age),b(age),c(a)//初始化列表紧跟定义
{
...
}
初始化列表中还可以传参数调用基类的构造函数。
class person
{
public:
int age;
int height;
person(int age_1,int height_1){age=age_1;height=height_1};
...
private:
...
protected:
...
};
class student:public person
{
public:
int weight;
student(int age_1,int height_1,int weight_1):person(age_1,height_1),weight=weight_1{};
}
//调用基类构造函数person,同时还给weight赋值
上面age_1,height_1,weight_1作为参数传入派生类student的构造函数,然后调用基类person的构造函数person,将参数age_1,height_1传入,如此一来,派生类的age,height就赋值完成。
注意:基类早于派生类构造,晚于派生类释放。所以构造一个派生类对象,在调用构造函数之前,会进行其基类构造函数的调用。而在基类调用析构函数总是在派生类之后。
8、虚继承
如果发生这种情况
B、C继承A,D继承B、C。A中一个变量base在B、C中都存在,那么在D中会存在两个base,一个从B继承来,一个从C继承来。
这两个继承过来的base对应两个不同的存储空间,所以他们两者是独立的。
这样会引起歧义,D.base到底是哪一个?
于是我们申明虚继承,让原本是B::base和C::base的地方变成指针,指向同一个D::base,这样就消除了歧义,使得D的base唯一
申明时,在继承类型和基类之 前加上"virtual"
class D:virtual public B, virtual public C
{
public:
...
private:
...
protected:
...
};
类的运算符重载
引入
实际上,c++的库中已经对很多符号进行重载。
比如+号,当a+b时,编译器会根据a的类型来匹配相应的+号的重载类型
当a是int,+号重载为“整型的+”,即把两个int相加
当a是double,+号重载为“浮点型的+”,即把两个double相加
一、重载原则
1、不改变运算符优先级,包括优先结合性
2、不改变操作数个数
3、语义基本不变
解释
+号重载之后的优先级总不能高于×号吧?(不改优先级)
+号重载之后总不会只对一个数想加吧?(不改操作数个数)
+号重载之后总不会解释是-号吧?(语义不能太大改变)
能够重载的运算符
| + | - | * | / |
| % | ^ | & | | |
| ~ | ! | = | < |
| > | += | -= | *= |
| /= | %= | ^= | &= |
| |= | << | >> | >>= |
| <<=< td> | == | != | <=< td> |
| >= | && | || | ++ |
| -- | ->* | ' | -> |
| [] | () | new | delete |
不能重载的运算符
| . | .* | :: | ?: | sizeof |
二、重载语法
<function_type> <classname>::operator <sign>(parameter)
{
......
}
举个例子
class location//位置类,包含横纵坐标两个属性
{
public:...
private:
int x;//横坐标
int y;//纵坐标
};
void location::operator ++(int &x,int &y)//重载符号++
{
x++;
y++;
}
上面的例子把++作为重载符号,让一旦有location类的对象调用这个重载的符号++就调用函数,使得横纵坐标都+1。
三、重载符号在主函数的使用
对于上面位置类location的++重载
int main()
{
location point(1,2);//设置point x=1,y=2
point++;//point是location类的对象,使用了重载后的++,调用函数,使得x++,y++,最终结果是x=2,y=3
}
方法就是直接在主函数中对象后面接上重载的符号
四、成员函数重载符号与友元函数重载符号
成员函数和友元函数用来重载符号虽然功能大致是相同的,但是调用重载的方式不一样,导致对于单操作数符号没什么问题,但对于双操作数符号就有可能出问题了。有必要指出,以下都是对双操作数符号解释
1、什么是双操作数符号?
+-×÷号有一左一右两个数,对两个数进行操作,称为双操作数符号(二元运算符),符号左边的叫左操作数,右边的叫右操作数。++,--只对一个数操作,所以称为单操作数符号(一元运算符)。
2、成员函数重载符号
成员函数重载符号,符号的左右操作数地位不想等,左边用于调用自身对象的成员函数,右边用于作为参数传入
成员函数重载的符号在被使用时,由左操作数调用函数,举个例子,空间向量
class the_vector//空间向量类,包含x,y,z
{
public:
the_vector(int,int,int);
the_vector operator+(the_vector,the_vector);
private:
int x;
int y;
int z;
protected:
};
the_vector::the_vector(int x1,int y1,int z1)//构造函数设置空间向量
{
x=x1;
y=y1;
z=z1;
}
the_vector the_vector::operator+(the_vector a,the_vector b)//重载为向量+号,使a向量和b向量相加,
{
the_vector sum;
sum.x=a.x+b.x;
sum.y=a.y+b.y;
sum.z=a.z+b.z;
return sum;
}
int main()
{
the_vector m(1,1,1);//定义m向量为(1,1,1)
the_vector n(2,1,3);//定义n向量为(2,1,3)
p=m+n;//p直接把两个向量相加,得到(3,2,4)
}
在上面的例子中,m+n这个操作进行的时候,由于这是成员函数重载+号,所以+号由m(左操作数)调用,所以在匹配+的重载会使用和m(左操作数)相同的类型的+号(即重载的向量+号)
所以m调用重载+函数实际上就是:m.operator+(n)
相当于把n作为参数传入对象m的成员函数
the_vector m(1,1,1);//定义m向量(1,1,1)
m+1;//等价于下面的写法
p=m.operator+(1);//调用m的成员函数,把1作为参数传入,最终得到p=(2,2,2)
但是如果
the_vector m(1,1,1);//定义m向量(1,1,1)
1+m;
就错了
左操作数是1,编译器会自动匹配整型的+号,然而右边不是整型而是一个类,就错了。
3、友元函数重载符号
使用友元函数来重载符号可以避免这种左右操作数地位不对等的问题
友元函数重载符号的左右两个操作数的地位都是对等的,都是作为参数传入
为什么?
前面说过,友元函数不属于类。所以就不存在成员函数的由左操作数调用对象的问题,两个操作数都变成参数传入友元函数
例子
class the_vector//空间向量类,包含x,y,z
{
public:
the_vector(int,int,int);
friend the_vector operator+(the_vector,the_vector);
private:
int x;
int y;
int z;
protected:
};
the_vector::the_vector(int x1,int y1,int z1)//构造函数设置空间向量
{
x=x1;
y=y1;
z=z1;
}
the_vector operator+(the_vector a,the_vector b)//友元重载为向量+号,使a向量和b向量相加,
{
the_vector sum;
sum.x=a.x+b.x;
sum.y=a.y+b.y;
sum.z=a.z+b.z;
return sum;
}
int main()
{
the_vector m(1,1,1);//定义m向量为(1,1,1)
p=m+1;//等价于operator+(m,1)
p=1+m;//等价于operator+(m,1)
}
这样子就正确了,m和1都是参数,地位同等,满足交换律,其中1会自动被转换成the_vector类(1,1,1)
注意:友元函数不能使用= () [] ->重载
4、几个特殊的运算符
(1)++和--
cpp规定,前置++是一元运算符,后置++是二元运算符。
为什么要这么规定?
用于区分前置的++和后置的++,程序员想让前置的++和后置的++有不同的作用,所以要让编译器知道要调用哪个
举个例子,我想让前置++先自增再赋值,后置++先赋值后自增
class the_vector
{
public:
friend the_vector operator++(the_vector&);//前置++,一元运算符
friend the_vector operator++(the_vector&,int);//后置++,二元运算符,多了个int
friend void operator+(the_vector&,the_vector);//把后者加到前者
friend void operator-(the_vector&,the_vector);//把前者减掉后者
...
private:
...
protected:
...
};
void operator+(the_vector &A,the_vector B)
{
A.x+=B.x;
A.y+=B.y;
A.z+=B.z;
}
void operator-(the_vector &A,the_vector B)
{
A.x-=B.x;
A.y-=B.y;
A.z-=B.z;
}
the_vector operator++(the_vector &A)//前置++
{
A+1;//先自增
return A;//再返回自增之后的数
}
the_vector operator++(the_vector &A,int x=1)
{
A+1;//先自增
return (A-1);//再返回自增之前的数
}
int main()
{
the_vector m(1,2,3);
the_vector m(1,2,3);
p=++m;
q=n++;
}
结果:p=(2,3,4),q=(1,2,3)
m自增之后的值赋给p,n自增之前的值赋给q
而编译器区分前置和后置的++符号就是通过参数的个数不同来区分应该调用哪个函数。
后置的++定义中的int x=1实际上就是个“伪参数”,作用仅仅是区分重载
(2)=赋值运算符的重载
“=”用于对象的复制,只能用成员函数重载,因为赋值是基于对象操作的,友元不属于类或者对象
重载赋值号“=”和拷贝构造函数的区别:拷贝构造函数在新建对象时进行赋值和初始化,重载赋值符号在作用域内任何时候都可以赋值给对象的数据成员
格式如下:
class type
{
public:
type & operator=(type);//“=”号赋值的构造函数重载
...
private:
int x;
int y;
...
protected:
...
};
type & type::operator=(type A)
{
this->x=A.x;
this->y=A.y;
return *this;//返回this指针所指的对象,就是当前对象
}
int main()
{
}
为什么要返回引用?(返回值类型为"type &")
返回引用可以连续调用重载的“=”进行赋值。A1=A2=A3...=An可以连续这么赋值,而且其中的所有“=”都是自己重载的版本
如果不返回引用就不能使用自己重载版本的A1=A2=A3...=An这么连续赋值.这样"="都是cpp自带的赋值方法
既然cpp自带赋值功能为什么还要自己重载?请往下看
深复制和浅复制
cpp自带的赋值的方法就称为“浅复制”,因为有时候自带的赋值方法会翻车,举个例子
某一个类,其中有一个数据成员是指针,现在对这个类新开一个对象A,A的指针用于指向存储A的某个数据的内存。现在用cpp版本的“浅复制”,复制出一个B,这个B的指针也只是简单的从A那里复制过来,所以A和B的这个指针指向同一片内存
这样导致A和B的指针共同修改A的某个数据。
然而我并不想这样,我想给B单独开一个储存这个数据的空间,然后让B的指针指向这个空间才对。这样需要“深复制”
深复制就是人为进行复制,把用cpp版本复制会出问题的部分单独拎出来,单独进行相关操作。
无论是复制构造函数,还是重载赋值号,都是一个重要的问题。(只不过一般不会碰到这种情况)
(3)流插入和提取运算符>>和<<
class the_vector
{
public:
friend ostream & operator<<(ostream &,const the_vector&);//流提取<<重载
friend istream & operator>>(istream &,the_vector&);//流插入>>重载
...
protected:
...
private:
int x;
int y;
int z;
}
ostream & operator<<(ostream &output,const the_vector& a)
{
output<<a.x<<" "<<a.y<<" "<<a.z;
}
istream & operator>>(istream &intput,the_vector&a)
{
input>>a.x>>a.y>>a.z;
}
int main()
{
the_vector m;
cin>>m;
cout<<m;
}
这样就实现了cin>>m直接对对象m输入对象的内容,cout<< m 直接输出对象的内容
流插入和流提取必须重载为友元函数。
参数中ostream加上const是为了保险一点防止被修改
其中的istream和ostream是IO类中的东西。
返回类型为ostream &,就是把output这一段输出流中的东西插入到ostream输出流中,而函数体中就是把x,y,z插入output流中,istream同理。
必须要返回对象的引用 &,这样才能继续参与主函数中的cout<< 的运算。